Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 34 additions & 22 deletions js/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -9,36 +9,20 @@ help:
@echo " make install-optional-deps - Install optional dependencies"
@echo " make test - Run all tests (core + wrappers + ai-sdk)"
@echo " make test-core - Run core tests only"
@echo " make test-ai-sdk - Run AI SDK tests (v1 + v2)"
@echo " make test-otel - Run OpenTelemetry tests (both installed and missing)"
@echo " make test-otel-installed - Run OpenTelemetry tests with library installed"
@echo " make test-otel-missing - Run OpenTelemetry tests without library"
@echo " make test-openai - Run OpenAI wrapper tests"
@echo " make test-anthropic - Run Anthropic wrapper tests"
@echo " make test-google-genai - Run Google GenAI wrapper tests"
@echo " make test-ai-sdk-4 - Run AI SDK v4 wrapper tests"
@echo " make test-ai-sdk-5 - Run AI SDK v5 wrapper tests"
@echo " make test-mastra - Run Mastra wrapper tests"
@echo " make test-claude-agent-sdk - Run Claude Agent SDK wrapper tests"
@echo " make bench - Run queue performance benchmarks"
@echo " make test-latest - Run core + latest versions of wrappers"

.PHONY: help bench build clean test test-core test-otel test-otel-installed test-otel-missing test-openai test-anthropic test-google-genai test-ai-sdk test-latest install-optional-deps publish-beta-local

# ------------------------------------------------------------------------------------------------- #
# AI SDK testing
# -------------------------------------------------------------------------------------------------
.PHONY: test-ai-sdk test-ai-sdk-v1 test-ai-sdk-v2

test-ai-sdk: test-ai-sdk-v1 test-ai-sdk-v2

test-ai-sdk-v1:
pnpm prune
$(call pnpm_install_no_save,ai@^3.0.0)
pnpm test:ai-sdk-v1

test-ai-sdk-v2:
pnpm prune
npm_config_save=false npm_config_lockfile=false pnpm add ai@beta @ai-sdk/openai@beta @ai-sdk/anthropic@beta @ai-sdk/provider@beta
pnpm test:ai-sdk-v2
# this should still work
pnpm test:ai-sdk-v1
.PHONY: help bench build clean test test-core test-otel test-otel-installed test-otel-missing test-openai test-anthropic test-google-genai test-ai-sdk-4 test-ai-sdk-5 test-mastra test-claude-agent-sdk test-latest install-optional-deps publish-beta-local

# ------------------------------------------------------------------------------------------------- #
# Anthropic testing
Expand Down Expand Up @@ -66,6 +50,34 @@ test-google-genai-latest:
$(call pnpm_install_no_save,@google/genai)
pnpm test:google-genai

# -------------------------------------------------------------------------------------------------
# AI SDK v4 testing
# -------------------------------------------------------------------------------------------------

test-ai-sdk-4:
cd src/wrappers/ai-sdk-4 && pnpm install && pnpm test

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i wonder if we could just have the main package.json delegate to the underlying ones.

i.e.

"test": "pnpm test:ai-sdk-v4 && ...",
"test:ai-sdk-v4": "cd src/wrappers/ai-sdk-v4 && pnpm test",

tl;dr i wonder if there's a nice way to get this without each of these make targets (always seemed odd)

@knjiang Ken Jiang (knjiang) Oct 21, 2025 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, i'd be fine with just moving all of this in package.json. I'll make a ticket


# -------------------------------------------------------------------------------------------------
# AI SDK v5 testing
# -------------------------------------------------------------------------------------------------

test-ai-sdk-5:
cd src/wrappers/ai-sdk-5 && pnpm install && pnpm test

# -------------------------------------------------------------------------------------------------
# Mastra testing
# -------------------------------------------------------------------------------------------------

test-mastra:
cd src/wrappers/mastra && pnpm install && pnpm test

# -------------------------------------------------------------------------------------------------
# Claude Agent SDK testing
# -------------------------------------------------------------------------------------------------

test-claude-agent-sdk:
cd src/wrappers/claude-agent-sdk && pnpm install && pnpm test


# -------------------------------------------------------------------------------------------------
# OpenTelemetry testing
Expand Down Expand Up @@ -124,7 +136,7 @@ test-core:
pnpm test

# Test everything
test: test-core test-otel test-openai test-anthropic test-ai-sdk test-google-genai
test: test-core test-otel test-openai test-anthropic test-google-genai

# Test the core and the latest versions of wrappers.
test-latest: test-core test-otel test-anthropic-latest test-openai-latest test-google-genai
Expand Down
11 changes: 3 additions & 8 deletions js/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "braintrust",
"version": "0.4.6",
"version": "0.4.7",
"description": "SDK for integrating Braintrust",
"repository": {
"type": "git",
Expand Down Expand Up @@ -56,17 +56,12 @@
"watch": "tsup --watch",
"clean": "rm -r dist/* && rm -r dev/dist/*",
"docs": "npx typedoc --options typedoc.json src/index.ts",
"test": "vitest run --exclude src/wrappers/anthropic.test.ts --exclude src/wrappers/oai.test.ts --exclude src/otel.test.ts --exclude src/otel-no-deps.test.ts --exclude src/wrappers/ai-sdk-v1.test.ts --exclude src/wrappers/ai-sdk-v2.test.ts --exclude src/wrappers/ai-sdk-v3.test.ts --exclude src/wrappers/mastra.test.ts --exclude src/wrappers/claude-agent-sdk.test.ts --exclude src/wrappers/google-genai.test.ts",

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the main win from this is we are now always running all the wrapper tests with conflicting deps in CI now. I've also moved the code around to be more understandable.

"test": "vitest run --exclude src/wrappers/anthropic.test.ts --exclude src/wrappers/oai.test.ts --exclude src/otel.test.ts --exclude src/otel-no-deps.test.ts --exclude src/wrappers/google-genai.test.ts",
"test:anthropic": "vitest run src/wrappers/anthropic.test.ts",
"test:openai": "vitest run src/wrappers/oai.test.ts",
"test:otel": "vitest run src/otel.test.ts",
"test:google-genai": "vitest run src/wrappers/google-genai.test.ts",
"test:otel-no-deps": "vitest run src/otel-no-deps.test.ts --reporter=verbose",
"test:ai-sdk-v1": "vitest run src/wrappers/ai-sdk-v1.test.ts",
"test:ai-sdk-v2": "vitest run src/wrappers/ai-sdk-v2.test.ts src/wrappers/ai-sdk-v1.test.ts",
"test:ai-sdk-v3": "vitest run src/wrappers/ai-sdk-v3.test.ts",
"test:mastra": "vitest run src/wrappers/mastra.test.ts",
"test:claude-agent-sdk": "vitest run src/wrappers/claude-agent-sdk.test.ts"
"test:otel-no-deps": "vitest run src/otel-no-deps.test.ts --reporter=verbose"
},
"author": "",
"license": "MIT",
Expand Down
10 changes: 5 additions & 5 deletions js/src/exports-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,12 @@ export * from "./framework2";
export * as graph from "./graph-framework";
export { LazyValue } from "./util";
export * from "./wrappers/oai";
export { BraintrustMiddleware } from "./wrappers/ai-sdk-v2";
export { wrapAISDKModel } from "./wrappers/ai-sdk-v1";
export { wrapAISDK } from "./wrappers/ai-sdk-v3";
export { wrapMastraAgent } from "./wrappers/mastra";
export { BraintrustMiddleware } from "./wrappers/ai-sdk-5/middleware";
export { wrapAISDKModel } from "./wrappers/ai-sdk-4/ai-sdk";
export { wrapAISDK } from "./wrappers/ai-sdk-5/ai-sdk";
export { wrapMastraAgent } from "./wrappers/mastra/mastra";
export { wrapAnthropic } from "./wrappers/anthropic";
export { wrapClaudeAgentSDK } from "./wrappers/claude-agent-sdk";
export { wrapClaudeAgentSDK } from "./wrappers/claude-agent-sdk/claude-agent-sdk";
export { wrapGoogleGenAI } from "./wrappers/google-genai";
export {
AISpanProcessor,
Expand Down
153 changes: 153 additions & 0 deletions js/src/wrappers/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
# Braintrust SDK Wrappers

This directory contains wrappers for various AI frameworks and SDKs. All wrappers are **exported from the main `braintrust` package** and ship as part of the regular npm distribution.

## Directory Structure

### Standard Wrappers

Most wrappers are single files that live directly in this directory:

- `anthropic.ts` - Anthropic SDK wrapper
- `oai.ts` - OpenAI SDK wrapper
- `google-genai.ts` - Google GenAI wrapper
- etc.

### Isolated Test Wrappers

Some wrappers have their own subdirectories with `package.json` files:

- `ai-sdk-4/` - AI SDK v4.x wrapper
- `ai-sdk-5/` - AI SDK v5.x wrapper (middleware + utilities)
- `mastra/` - Mastra agent wrapper
- `claude-agent-sdk/` - Claude Agent SDK wrapper

**Important**: These subdirectories contain **private test-only packages** (`"private": true`). The code is still exported from the main `braintrust` package - the separate `package.json` files exist solely to run tests with specific dependency versions.

## Why Separate package.json Files?

Some integrations need to be tested against specific versions of their underlying SDKs:

- **AI SDK v4 vs v5**: Breaking changes between major versions require separate test environments
- **Mastra**: Uses AI SDK v5 and has specific version requirements
- **Claude Agent SDK**: Has its own versioning independent of Anthropic's main SDK

### What Gets Published?

**Everything in this directory ships in the main `braintrust` npm package.** The subdirectory `package.json` files are marked `"private": true` and are **never published to npm**.

Users install just one package:

```bash
npm install braintrust
```

And can import any wrapper:

```typescript
import { wrapAISDK, wrapMastraAgent, wrapClaudeAgentSDK } from "braintrust";
```

### Running Tests

**Main package tests:**

```bash
cd js
pnpm test # Excludes subdirectory tests
```

**Subdirectory tests** (with isolated dependencies):

```bash
cd js/src/wrappers/ai-sdk-4 && pnpm test
cd js/src/wrappers/ai-sdk-5 && pnpm test
cd js/src/wrappers/mastra && pnpm test
cd js/src/wrappers/claude-agent-sdk && pnpm test
```

## Shared Utilities

### `ai-sdk-shared/`

Contains shared utility functions used by AI SDK wrappers and other integrations:

- `utils.ts` - Common utilities for AI SDK integrations (token counting, provider detection, etc.)
- `utils.test.ts` - Tests for shared utilities

These utilities are imported by:

- `ai-sdk-4/` and `ai-sdk-5/` wrappers
- `mastra/` wrapper (uses AI SDK v5)
- `claude-agent-sdk/` wrapper (for token utilities)

## Adding a New Wrapper

### For Standard Wrappers (Single File)

1. Create `your-wrapper.ts` in this directory
2. Create `your-wrapper.test.ts` for tests
3. Export from `src/exports-node.ts`:
```typescript
export { wrapYourSDK } from "./wrappers/your-wrapper";
```
4. Add test exclusion to main `package.json` if needed

### For Isolated Test Wrappers (Separate package.json)

Only needed if you require specific SDK versions for testing:

1. Create subdirectory: `js/src/wrappers/your-wrapper/`
2. Move files: `your-wrapper.ts`, `your-wrapper.test.ts`
3. Create `package.json` (marked `"private": true`) with specific dependencies
4. Create `vitest.config.js` and `tsconfig.json` (copy from `ai-sdk-4/`)
5. Fix imports to use relative paths (`../../logger`, `../ai-sdk-shared/utils`, etc.)
6. Update `src/exports-node.ts`:
```typescript
export { wrapYourSDK } from "./wrappers/your-wrapper/your-wrapper";
```
7. Run `pnpm install` in the subdirectory

## GitHub Actions

All wrapper subdirectories with their own `package.json` are tested automatically in CI via a single workflow file: `.github/workflows/reusable-test-sdk-wrappers.yaml`

### How It Works

The workflow uses a **matrix strategy** to test all wrappers in parallel:

```yaml
strategy:
fail-fast: false
matrix:
wrapper:
- ai-sdk-4
- ai-sdk-5
- mastra
- claude-agent-sdk
```

Each wrapper gets its own job that:

1. Checks out the repository
2. Sets up Node.js and pnpm
3. Installs dependencies in the wrapper's subdirectory
4. Runs `pnpm test` with the isolated dependencies

### Adding a New Wrapper to CI

When adding a new isolated test wrapper:

1. Follow the steps in "Adding a New Wrapper" above
2. Add the wrapper directory name to the matrix in `.github/workflows/reusable-test-sdk-wrappers.yaml`:
```yaml
matrix:
wrapper:
- ai-sdk-4
- ai-sdk-5
- mastra
- claude-agent-sdk
- your-new-wrapper # Add here
```

The workflow is integrated into the main CI pipeline (`.github/workflows/braintrust-ci.yaml`) and runs automatically on pull requests.
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
import { describe, it, expect, vi } from "vitest";
import {
wrapAISDKModel,
postProcessOutput,
postProcessPrompt,
} from "./ai-sdk-v1";
import { BraintrustMiddleware } from "../exports-node";
import { wrapAISDKModel, postProcessOutput, postProcessPrompt } from "./ai-sdk";
import { BraintrustMiddleware } from "../../exports-node";
import {
LanguageModelV1Prompt,
LanguageModelV1FunctionToolCall,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import {
type ChatCompletionContentPartType as ContentPart,
type ChatCompletionMessageParamType as Message,
type ChatCompletionToolType as ChatCompletionTool,
} from "../generated_types";
import { startSpan } from "../logger";
import { getCurrentUnixTimestamp, isEmpty } from "../util";
} from "../../generated_types";
import { startSpan } from "../../logger";
import { getCurrentUnixTimestamp, isEmpty } from "../../util";
import {
LanguageModelV1,
LanguageModelV1CallOptions,
Expand All @@ -22,7 +22,7 @@ import {
LEGACY_CACHED_HEADER,
parseCachedHeader,
X_CACHED_HEADER,
} from "./oai";
} from "../oai";

/**
* Wrap an ai-sdk model (created with `.chat()`, `.completion()`, etc.) to add tracing. If Braintrust is
Expand Down
19 changes: 19 additions & 0 deletions js/src/wrappers/ai-sdk-4/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "@braintrust/ai-sdk-4-tests",
"version": "0.0.1",
"private": true,
"description": "Test infrastructure for AI SDK v4.x compatibility",
"scripts": {
"test": "vitest run"
},
"dependencies": {
"@ai-sdk/provider": "^1.1.3"
},
"devDependencies": {
"ai": "4.3.19",
"@types/node": "^20.10.5",
"typescript": "5.4.4",
"vitest": "^2.1.9",
"vite-tsconfig-paths": "^4.3.2"
}
}
16 changes: 16 additions & 0 deletions js/src/wrappers/ai-sdk-4/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"baseUrl": ".",
"rootDir": "../../..",
"outDir": "./dist"
},
"include": [
"./**/*.ts",
"../../../src/**/*.ts"
],
"exclude": [
"node_modules/**",
"dist/**"
]
}
9 changes: 9 additions & 0 deletions js/src/wrappers/ai-sdk-4/vitest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import tsconfigPaths from "vite-tsconfig-paths";

const config = {
plugins: [tsconfigPaths()],
resolve: {
extensions: [".ts", ".tsx", ".mts", ".js", ".mjs", ".jsx", ".json"],
},
};
export default config;
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ import { openai } from "@ai-sdk/openai";
import { anthropic } from "@ai-sdk/anthropic";
import { z } from "zod/v3";

import { wrapAISDK } from "./ai-sdk-v3";
import { wrapAISDK } from "./ai-sdk";
import {
_exportsForTestingOnly,
Logger,
TestBackgroundLogger,
initLogger,
} from "../logger";
} from "../../logger";

const OPENAI_MODEL = "gpt-4o-mini";
const ANTHROPIC_MODEL = "claude-3-haiku-20240307";
Expand Down
Loading
Loading